﻿using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using VK.Exceptions;
using VK.TransportClient;
using VK.TransportClient.Curl;
using JsonSerializer = Newtonsoft.Json.JsonSerializer;

namespace VK.Client
{
    public class VkApiRequest
    {
        
        private const string PARAM_VERSION = "v";
        private const string PARAM_ACCESS_TOKEN = "access_token";
        private const string PARAM_LANG = "lang";

        private const string KEY_ERROR = "error";
        private const string KEY_RESPONSE = "response";

        protected const int CONNECTION_TIMEOUT = 10;
        protected const int HTTP_STATUS_CODE_OK = 200;
        
        private string host;
        
        private CurlHttpClient http_client;
        
        private string version;
        
        private string language;
        public VkApiRequest(string apiVersion, string language, string host, HttpMessageHandler? handler = null)
        {
            http_client = new CurlHttpClient(CONNECTION_TIMEOUT, handler);
            version = apiVersion;
            this.host = host;
            this.language = language;
        }

        public Task<VkResponse<JContainer>> post(string method, string access_token,
            Dictionary<string, object> @params = null) => post<JContainer>(method, access_token, @params);
        public async Task<VkResponse<T>> post<T>(string method, string access_token, Dictionary<string, object> @params = null) {
            var tmpParams = formatParams(@params);
            tmpParams[PARAM_ACCESS_TOKEN] = access_token;

            if (!tmpParams.ContainsKey(PARAM_VERSION)) {
                tmpParams[PARAM_VERSION] = version;
            }

            if (!string.IsNullOrEmpty(language) && !tmpParams.ContainsKey(PARAM_LANG)) {
                tmpParams[PARAM_LANG] = language;
            }

            var url = host + '/' + method;

            TransportClientResponse response;
            try {
                response = await http_client.PostAsync(url, new FormUrlEncodedContent(tmpParams));
            } catch (TransportRequestException e) {
                throw new VKClientException(e);
            }

            try
            {
                var res = await parseResponse<T>(response);
                return new VkResponse<T>()
                {
                    State = true,
                    Content = res
                };
            }
            catch (Exception e)
            {
               return new VkResponse<T>()
               {
                   State = false,
                   Error = e.Message
               };
            }
        }

        private Task<JContainer> parseResponse(TransportClientResponse response) =>
            parseResponse<JContainer>(response);
        private async Task<T> parseResponse<T>(TransportClientResponse response) {
            
            var body = response.Body;
            var decode_body = await decodeBody(body);

            if (decode_body.TryGetValue(KEY_ERROR, out var errorJson)) {
                var error = errorJson.ToObject<VKApiError>();
                throw VK.Exceptions.Api.ExceptionMapper.parse(error);
            }

            var j = new JsonSerializer
            {
                ContractResolver = new DefaultContractResolver() {NamingStrategy = new SnakeCaseNamingStrategy()}
            };
            return decode_body.ContainsKey(KEY_RESPONSE) ? decode_body[KEY_RESPONSE].ToObject<T>(j) : decode_body.ToObject<T>(j);
        }
        
        private Dictionary<string, string> formatParams(Dictionary<string, object> @params)
        {
            var res = new Dictionary<string, string>();
            if (@params == null) return res;
            
            foreach (var (key, value) in @params)
            {
                res[key] = value switch
                {
                    IEnumerable<string> arr => string.Join(',', arr),
                    bool b => b ? "1" : "0",
                    string s => s,
                    _ => res[key]
                };
            }
            return res;
        }
        
        protected async Task<JObject> decodeBody(HttpContent body)
        {
            var response = await body.ReadAsStringAsync();
            
            var decoded_body = JObject.Parse(response);

            if (decoded_body == null) {
                decoded_body = new JObject();
            }

            return decoded_body;
        }
    }

    public class VkResponse<TRes>
    {
        public bool State { get; set; }
        public string Error { get; set; }
        public TRes Content { get; set; }
    }
}